#if !defined(AFX_STRINGTOKENIZER_H__4AEB602A_CB5F_44A3_8A99_76BD849EE25B__INCLUDED_)
#define AFX_STRINGTOKENIZER_H__4AEB602A_CB5F_44A3_8A99_76BD849EE25B__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

// File Name:	StringTokenizer.h
// Version:		0.1
// Purpose:		Declaration and implementation of the string tokenizer class.
// Author:		Nick Alexeev
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed unmodified by any means PROVIDING it is 
// not sold for profit without the authors written consent, and 
// providing that this notice and the authors name is included. If 
// the source code in  this file is used in any commercial application 
// then a simple email would be nice.
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage whatsoever.
// It's free - so you get what you pay for.


/////////////////////////////////////////////////////////////////////////////
// CStringTokenizer declaration

template <class T>					// T is either a CStringArray or CStringList
class CStringTokenizer : public T
{
public:
	enum OPTIONS
	{
		IGNORE_EMPTY_TOKENS = 0x01,	// Empty tokens (which occur when there are 2 consecutive delimiters) are not added to the collection
		TERMINATING_STRING = 0x02	// Tokenization ends when a certain string is encountered - not just a single character 
	};

	CStringTokenizer();
	UINT Tokenize(CString&	strSrc, LPCTSTR pStrDelimit, LPCTSTR strTerminate = '\0', UINT iOffset = 0);
	virtual ~CStringTokenizer() {;}	
	void AddOptions(OPTIONS iOptions)		{m_iFlags |= iOptions;}
	void RemoveOptions(OPTIONS iOptions)	{m_iFlags &= !iOptions;}

protected:
	void Add(LPCTSTR pStrNew);	// helper function with 2 separate implementations for CStringArray and CStringList (template specialization)

	UINT	m_iFlags;			// tokenization options
	CMutex	m_mtxTokenize;		// serves to make the Tokenize() method non-reentrant
};


/////////////////////////////////////////////////////////////////////////////
// CStringTokenizer implementation

// PURPOSE:			Checks the type of the parent class T.
// PRECONDITIONS:	T is either CStringArray or CStringList
template <class T>
CStringTokenizer<T>::CStringTokenizer()
	: m_mtxTokenize(FALSE)		// the mutex is free for the taking
{
	m_iFlags = IGNORE_EMPTY_TOKENS;

	CRuntimeClass* pRTC = T::GetRuntimeClass();
	if (RUNTIME_CLASS(CStringArray) == pRTC)	return;	// admissible parent class
	if (RUNTIME_CLASS(CStringList) == pRTC)		return;	// same as above
	ASSERT(FALSE);
}

// PURPOSE:			Tokenize the string and APPEND the tokens to the parent collection class
// POSTCONDITIONS:	The original string remains intact.  The method can throw CMemoryException.
template <class T>
UINT				// Offset of the next character after the terminator.
					// The return value and the iOffset parameter can be used for parsing a sting with successive calls to Tokenize().
CStringTokenizer<T>::Tokenize(CString&	strSrc,			// [in] a string that will be tokenized. 
							  LPCTSTR	pStrDelimit,	// [in] a set of delimiting characters
							  LPCTSTR	pStrTerminate,	// [in] a set of terminating characters, or a terminating sequence, depending on options
							  UINT		iOffset)		// [in] Tokenization will start at this offset. Defaulted to zer0.  
{	
	USES_CONVERSION;
	ASSERT(pStrDelimit != NULL);
	ASSERT(iOffset <= strSrc.GetLength());

	m_mtxTokenize.Lock(1000);		// lock the mutex to prevent re-entrancy.  Timeout after 1 sec.

	LPTSTR	pStrSrc = NULL;			// pointer to the beginning of the source string buffer with offset

	TRY
	{		
		pStrSrc = strSrc.GetBuffer(strSrc.GetLength() + 1) + iOffset;	// GetBuffer() can, potentially, throw a memory exception, 
									// although that should never happen, because it's not allocating new memory for the buffer.
									// +1 is for the terminating Null.
	}
	CATCH(CMemoryException, pExc)	// catch memory exceprion thrown by GetBuffer()
	{
		strSrc.ReleaseBuffer();
		m_mtxTokenize.Unlock();		// unlock the mutex to let the other threads use the tokenizer
		return 0;
	}
	END_CATCH
	
	// find the end of the chunk of a string that will be tokenized	
	LPTSTR	pTerm = NULL;			// pointer to the terminating character (or string) in the original string
	LPTSTR	pTemp = NULL;
	if (m_iFlags & TERMINATING_STRING)
	{
		pTerm =  _tcsstr(pStrSrc, pStrTerminate);
	}
	else
	{
		pTerm = pStrSrc + _tcscspn(pStrSrc, pStrTerminate);
	}
	
	CHAR	cTemp;					// temporary storage
	LPTSTR	pBegin, pEnd;			// pointers to beginning and the end of the of the current token

	TRY
	{		
		for(pBegin = pEnd = pStrSrc; pEnd < pTerm; pBegin = pEnd = pEnd + 1)
		{
			pEnd = _tcspbrk(pBegin, pStrDelimit);	// advance to the end of the current token
			if( !pEnd )	
			{
				Add(pBegin);
				break;
			}

			if (pEnd > pTerm) pEnd = pTerm;	// don't skip the end

			cTemp = *pEnd;					// save the original delimiting character
			*pEnd = '\0';					// temporarily replace the delimiting charater with a Null character to terminate a token.  Would that be a hack?..
			
			if (_tcslen (pBegin) > 0 || !(m_iFlags & IGNORE_EMPTY_TOKENS))	// if the token has any characters or if we don't care
				Add(pBegin);				// save the token // this can throw a memory exception		
			*pEnd = cTemp;					// put the original delimiting character back		
		}
		strSrc.ReleaseBuffer();
	}
	CATCH(CMemoryException, pExc)	// catch memory exceprion thrown by CStringArray::Add()
	{
		strSrc.ReleaseBuffer();
		THROW(pExc);				// rethrow to the calling code
		return 0;
	}
	END_CATCH

	m_mtxTokenize.Unlock();			// unlock the mutex to let the other threads use the tokenizer

	return (pTerm - pStrSrc + 1);
}

// PURPOSE:	A helper function, which does template specialization and resolves the difference between CStringArray and CStringlist
template <>
void
CStringTokenizer<CStringArray>::Add(LPCTSTR pStrNew)	// [in] a string to be added to array
{
	TRY
	{
		CStringArray::Add(pStrNew);
	}
	CATCH(CMemoryException, pExc)	
	{
		THROW(pExc);				// rethrow to the calling code
	}
	END_CATCH
}

// PURPOSE:	A helper function, which does template serialization and resolves the difference between CStringArray and CStringlist
template <>
void
CStringTokenizer<CStringList>::Add(LPCTSTR pStrNew)	// [in] a string to be added to array
{
	TRY
	{
		CStringList::AddTail(pStrNew);
	}
	CATCH(CMemoryException, pExc)	
	{
		THROW(pExc);				// rethrow to the calling code
	}
	END_CATCH
}

#endif // !defined(AFX_STRINGTOKENIZER_H__4AEB602A_CB5F_44A3_8A99_76BD849EE25B__INCLUDED_)
